package com.cmw.http.server;

import com.cmw.http.model.HttpConfigModel;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Date;
import java.util.logging.Logger;

/**
 * 微型嵌入式HttpServer
 * @ClassName MicroHttpServer
 * @Description: TODO
 * @Author cheng
 * @Date 2020/6/3
 * @Version V1.0
 **/
public final class MicroHttpServer {
    private static final Logger LOG = Logger.getLogger("MicroHttpServer");
    private static HttpServerProvider PROVIDER_INSTANCE = null;
    private static HttpConfigModel httpConfig;
    public static void start(int port, HttpConfigModel httpConfigModel){
        stop();
        httpConfig = httpConfigModel;
        PROVIDER_INSTANCE = new HttpServerProvider(port);
        PROVIDER_INSTANCE.start();
    }

    public static void stop(){
        if(null != httpConfig) httpConfig = null;
        if(null == PROVIDER_INSTANCE) return;
        PROVIDER_INSTANCE.exit();
        PROVIDER_INSTANCE = null;
        LOG.info("MicroHttpServer stop ...");
    }


    private static class HttpServerProvider extends Thread{
        private final int port;
        private boolean done = false;
        private ServerSocket serverSocket = null;

        public HttpServerProvider(int port) {
            this.port = port;
        }

        @Override
        public void run() {
            super.run();
            try {
                ServerSocket serverSocket = new ServerSocket(port);
                LOG.info("MicroHttpServer Start ...");
                String ipAddress = serverSocket.getInetAddress().getHostAddress();
                LOG.info(String.format("http access url : http://%s:%d/", ipAddress, port));
                while (!done){
                    Socket socket = serverSocket.accept();
                    InputStream inputStream = socket.getInputStream();
                    InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                    BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                    try{
                        String reqeust = bufferedReader.readLine();
                        LOG.info(String.format("request:%s", reqeust)); //输出一下请求的报头
                        String[] httpInfoArr = null;
                        if(null != reqeust && !reqeust.equals("")){
                            httpInfoArr = reqeust.split(" ");//拆分出第一行的三部分内容
                        }

                        do{//循环是为了把剩下的请求内容读完，否则会产生异常
                            reqeust = bufferedReader.readLine();
                            LOG.info(String.format("request:%s", reqeust)); //把全部报头都输出
                        }while (null != reqeust && !reqeust.equals(""));
                        OutputStream outputStream = socket.getOutputStream();
                        if(null == httpInfoArr || httpInfoArr.length != 3 || !httpInfoArr[0].equals("GET")){
                            responseOutFileByErr(outputStream, HttpConfigModel.HTTP_STATUS_500);
                        }else{
                            String path = httpInfoArr[1];//取出请求的资源路径
                            responseOutFileByPath(path, outputStream);
                        }
                    }catch (Exception ex){
                        ex.printStackTrace();
                    }finally {
                        if(null != bufferedReader) bufferedReader.close();
                        if(null != inputStreamReader) inputStreamReader.close();
                        if(null != inputStream) inputStream.close();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                close();
            }
            LOG.info("MicroHttpServer Finish ...");
        }

        private void responseOutFileByPath(String path, OutputStream outputStream) throws IOException {
            boolean isHome = false;
            if(path.equals("/") || path.equals("/index")|| path.equals("/home")){
                path = httpConfig.getDefaultFile();
                isHome = true;
            }
            String filePath = httpConfig.getHomeDir()+"/" + path;
            if(isHome && (isEmpty(path) || !Files.exists(Paths.get(filePath)))){
                responseOutHtml(HttpConfigModel.UN_HOME_FILE_RESPONSE, outputStream, HttpConfigModel.HTTP_STATUS_200);
            }else{
                if (Files.exists(Paths.get(filePath))) { //判断文件是否存在 ，如果不存在，返回404页面
                    responseOutFile(filePath, outputStream, HttpConfigModel.HTTP_STATUS_200);
                } else { //如果文件不存在，返回404页面
                    responseOutFileByErr(outputStream, HttpConfigModel.HTTP_STATUS_404);
                }
            }
        }

        private void responseOutFileByErr(OutputStream outputStream, int httpStatus) throws IOException {
            String errContentHtml = null;
            switch (httpStatus){
                case HttpConfigModel.HTTP_STATUS_500:{
                    errContentHtml = HttpConfigModel.UN_ERR_FILE_RESPONSE;
                    break;
                }  case HttpConfigModel.HTTP_STATUS_404:{
                    errContentHtml = HttpConfigModel.UN_NOT_FOUND_FILE_RESPONSE;
                    break;
                }
            }

            String errFile = httpConfig.getErrFile();
            if(isEmpty(errFile) || !Files.exists(Paths.get(httpConfig.getHomeDir()+"/" + errFile))){
                if(isEmpty(errContentHtml)) errContentHtml = HttpConfigModel.UN_NOT_FOUND_FILE_RESPONSE;
                responseOutHtml(errContentHtml, outputStream, httpStatus);
            }else{
                responseOutFile(errFile, outputStream, httpStatus);
            }
        }

        public static void responseOutHtml(String content, OutputStream os, int status) throws IOException {
            System.out.println("response:");
            System.out.println(content);
            StringBuilder stringBuilder = getHttpHeaderSet(null, status);
            stringBuilder.append(content);
            os.write(stringBuilder.toString().getBytes("UTF-8"));  //输出响应报头
           // os.write(content.getBytes());
            os.flush();
            os.close();
        }

        public static void responseOutFile(String fileName, OutputStream os, int status) throws IOException {
            System.out.println("response:");
            System.out.println(fileName);
            StringBuilder stringBuilder = getHttpHeaderSet(fileName, status);
            FileInputStream fis = new FileInputStream(fileName);
            byte[] bytes = new byte[1024];
            int len;
            os.write(stringBuilder.toString().getBytes("UTF-8"));  //输出响应报头
            while ((len = fis.read(bytes)) != -1) { //输出文件内容
                os.write(bytes, 0, len);
            }
            os.flush();
            os.close();
            fis.close();
        }

        private static StringBuilder getHttpHeaderSet(String fileName,int status) {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("HTTP/1.1 " + status + " OK\r\n"); //输出状态码
            stringBuilder.append("Date: " + (new Date()).toString() + "\r\n");
            stringBuilder.append("Server: MyServer 0.0.1\r\n");  //代表服务器的软件名称
            stringBuilder.append("X-Powered-By: Hawkon\r\n");    //这行可以换成你的英文名，看起来会有点diao
            stringBuilder.append("Keep-Alive: timeout=5, max=100\r\n");
            stringBuilder.append("Connection: Keep-Alive\r\n"); //image/png
            String contentType = getContentType(fileName);
            stringBuilder.append(String.format("Content-Type: %s;charset=utf-8\r\n", contentType)); //字符串编码
            stringBuilder.append("\r\n");//多输出一个空行，用来分割报头和报体，HTTP协议要求
            return stringBuilder;
        }

        private static String getContentType(String fileName){
            if(isEmpty(fileName)){
                return "text/html";
            }else{
                fileName = fileName.toLowerCase();
                if(fileName.endsWith("png")){
                    return "image/png";
                }else if(fileName.endsWith("jpeg") || fileName.endsWith("jpe")|| fileName.endsWith("jpg")){
                    return "image/jpeg";
                }else if(fileName.endsWith("mp4") ){
                    return "video/mpeg4";
                }else if(fileName.endsWith("mp3") ){
                    return "audio/mp3";
                }else if(fileName.endsWith("mp2") ){
                    return "audio/mp2";
                }else if(fileName.endsWith("pdf") ){
                    return "application/pdf";
                }else{
                    return "text/html";
                }
            }
        }

        private  static boolean isEmpty(String str){
            return (null == str || str.equals("") || str.trim().equals(""));
        }

        public void close(){
            if(null == serverSocket) return;
            try {
                serverSocket.close();
                serverSocket = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public void exit(){
            close();
            done = true;
        }
    }
}
